import { newArrayProto } from "./array";
import Dep from "./dep";

class Observe {
  constructor(data) {
    // data可能是数组，也可能是对象。给每一个对象都依赖收集
    this.dep = new Dep();

    // 把this放到data对象中。如果数据对象上有__ob__，说明他被观测过了
    Object.defineProperty(data, "__ob__", {
      value: this,
      enumerable: false, // 把__ob__ 变得不可枚举，无法监测
    });

    // Object.defineProperty只能劫持已经存在的属性（vue里会为此单独写一些api，如$set、$delete)
    if (Array.isArray(data)) {
      // 重写数组7个变异方法方法，但也要保留数组原有的特性
      data.__proto__ = newArrayProto;

      this.observeArray(data);
    } else {
      this.walk(data);
    }
  }

  // 循环对象 对属性依次劫持
  walk(data) {
    // “重新定义” 属性（性能比vue3差的原因）
    Object.keys(data).forEach((key) => defineReactive(data, key, data[key]));
  }

  observeArray(data) {
    // 如果数组中放了对象，对象可以被监控到
    data.forEach((item) => observe(item));
  }
}

// 深度循环，如果数组内还有数组，也为该数组做依赖收集
function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    value[i].__ob__ && value[i].__ob__.dep.depend();
    if (Array.isArray(value[i])) {
      dependArray(value[i]);
    }
  }
}

export function defineReactive(target, key, value) {
  // 递归思想，如果value值的类型不是对象，则return；如果是对象，则继续劫持
  let childOb = observe(value); // childOb.dep用来收集依赖

  let dep = new Dep();

  // 此处value存放在闭包中，不会销毁
  Object.defineProperty(target, key, {
    // 取值执行get
    get() {
      if (Dep.target) {
        dep.depend(); // 让这个属性收集器记住当前的watcher

        if (childOb) {
          childOb.dep.depend(); // 让数组和对象本身也依赖收集

          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    // 修改值执行set
    set(newValue) {
      if (newValue === value) return;
      observe(newValue);
      value = newValue;
      dep.notify();
    },
  });
}

export function observe(data) {
  // 判断是否为对象，是则劫持该对象数据
  if (typeof data !== "object" || data == null) {
    return; // 只能对对象进行劫持
  }

  // 如果对象被劫持过了，那就不需要再被劫持了（要判断一个对象是否被劫持过，可以添加一个实例，用实例来判断是否被劫持过）
  if (data.__ob__ instanceof Observe) {
    // 已经被劫持过了
    return data.__ob__;
  }

  return new Observe(data);
}
